package com.payment.utils;

import com.payment.utils.payment.WxPayConstants;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.regex.Pattern;

public class WxPayUtil {

    private static Logger logger = Logger.getLogger(WxPayUtil.class);

    /**
     * 微信H5支付：统一下单
     *
     * @param body
     * @param out_trade_no
     * @param total_fee
     * @param IP
     * @param
     * @param
     * @return
     * @throws IOException
     */
    public static String UnifiedOrderH5(String body, String scene_info, String out_trade_no, Integer total_fee, String IP) throws IOException {
        // 设置访问路径
        HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder");
        // 随机
        String nonce_str = getNonceStr().toUpperCase();
        // 组装请求参数,按照ASCII排序

        String unifiedOrdersign =
                "appid=" + WxPayConstants.APPID +
                        "&body=" + body +
                        "&mch_id=" + WxPayConstants.MCH_ID +
                        "&nonce_str=" + nonce_str +
                        "&notify_url=" + WxPayConstants.NOTIFY_H5_URL +
                        "&out_trade_no=" + out_trade_no +
                        "&scene_info=" + scene_info +
                        "&spbill_create_ip=" + IP +
                        "&total_fee=" + total_fee.toString() +
                        "&trade_type=" + WxPayConstants.TRADE_TYPE_H5 +
                        "&key=" + WxPayConstants.KEY;
        // 这个字段是用于之后MD5加密的，字段要按照ascii码顺序排序
        String sign1 = MD5.calcMD5(unifiedOrdersign).toUpperCase();
        String sign = Md5Encrypt.md5(unifiedOrdersign).toUpperCase();
        String sign2 = HMACSHA256.sha256_HMAC(unifiedOrdersign, WxPayConstants.KEY).toUpperCase();
        logger.debug("H5统一下单参与签名的字符串是：" + unifiedOrdersign + ";H5下单MD5值:" + sign + ";H5下单Md5Encrypt值:" + sign1 + ";H5下单HMACSHA256值:" + sign2);
        // 组装包含openid用于请求统一下单返回结果的XML
        StringBuilder sb = new StringBuilder("");
        sb.append("<xml>");
        setXmlKV(sb, "appid", WxPayConstants.APPID);
        setXmlKV(sb, "body", body);
        setXmlKV(sb, "mch_id", WxPayConstants.MCH_ID);
        setXmlKV(sb, "nonce_str", nonce_str);
        setXmlKV(sb, "notify_url", WxPayConstants.NOTIFY_H5_URL);
        setXmlKV(sb, "out_trade_no", out_trade_no);
        setXmlKV(sb, "scene_info", scene_info);
        setXmlKV(sb, "spbill_create_ip", IP);
        setXmlKV(sb, "total_fee", total_fee.toString());
        setXmlKV(sb, "trade_type", WxPayConstants.TRADE_TYPE_H5);
        setXmlKV(sb, "sign", sign);
        sb.append("</xml>");
        logger.debug("H5统一下单请求：" + sb);
        // 这个处理是为了防止传中文的时候出现签名错误
        StringEntity reqEntity = new StringEntity(new String(sb.toString().getBytes("UTF-8"), "ISO8859-1"));
        httppost.setEntity(reqEntity);
        CloseableHttpClient httpclient = HttpClients.createDefault();
        CloseableHttpResponse response = httpclient.execute(httppost);
        String strResult = EntityUtils.toString(response.getEntity(), Charset.forName("utf-8"));
        logger.debug("H5统一下单返回xml：" + strResult);

        return strResult;
    }

    /**
     * 微信APP支付：统一下单
     *
     * @param body
     * @return map
     * @throws IOException
     */
    public static String UnifiedOrderAPP(String body, String orderId, Integer total_fee, String ipAddrStr, String APPID) throws Exception {
        // 设置访问路径
        HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder");
        // 随机
        String nonce_str = getNonceStr().toUpperCase();
        // 组装请求参数,按照ASCII排序

        String unifiedOrdersign =
                "appid=" + APPID +
                        "&body=" + body +
                        "&mch_id=" + WxPayConstants.MCH_ID +
                        "&nonce_str=" + nonce_str +
                        "&notify_url=" + WxPayConstants.NOTIFY_APP_URL +
                        "&out_trade_no=" + orderId +
                        "&spbill_create_ip=" + ipAddrStr +
                        "&total_fee=" + total_fee.toString() +
                        "&trade_type=" + WxPayConstants.TRADE_TYPE_APP +
                        "&key=" + WxPayConstants.KEY;

        // 这个字段是用于之后MD5加密的，字段要按照ascii码顺序排序
        String sign1 = MD5.calcMD5(unifiedOrdersign).toUpperCase();
        String sign = Md5Encrypt.md5(unifiedOrdersign).toUpperCase();
        String sign2 = HMACSHA256.sha256_HMAC(unifiedOrdersign, WxPayConstants.KEY).toUpperCase();
        logger.info("APP统一下单参与签名的字符串是：" + unifiedOrdersign + ";APP下单MD5值:" + sign + ";APP下单Md5Encrypt值:" + sign1 + ";APP下单HMACSHA256值:" + sign2);

        // 组装包含openid用于请求统一下单返回结果的XML
        StringBuilder sb = new StringBuilder("");
        sb.append("<xml>");
        setXmlKV(sb, "appid", APPID);
        setXmlKV(sb, "body", body);
        setXmlKV(sb, "mch_id", WxPayConstants.MCH_ID);
        setXmlKV(sb, "nonce_str", nonce_str);
        setXmlKV(sb, "notify_url", WxPayConstants.NOTIFY_APP_URL);
        setXmlKV(sb, "out_trade_no", orderId);
        setXmlKV(sb, "spbill_create_ip", ipAddrStr);
        setXmlKV(sb, "total_fee", total_fee.toString());
        setXmlKV(sb, "trade_type", WxPayConstants.TRADE_TYPE_APP);
        setXmlKV(sb, "sign", sign);
        sb.append("</xml>");
        logger.info("APP统一下单请求：" + sb);
        // 这个处理是为了防止传中文的时候出现签名错误
        StringEntity reqEntity = new StringEntity(new String(sb.toString().getBytes("UTF-8"), "ISO8859-1"));
        httppost.setEntity(reqEntity);
        CloseableHttpClient httpclient = HttpClients.createDefault();
        CloseableHttpResponse response = httpclient.execute(httppost);
        String strResult = EntityUtils.toString(response.getEntity(), Charset.forName("utf-8"));
        logger.info("APP统一下单返回xml：" + strResult);

        return strResult;
    }

    /*****
     * 微信扫二维码支付
     * @param body
     * @param orderId
     * @param totel_fee
     * @param ipAddrStr
     * @return
     */
    public static String UnifiedOrderScan(String body, String orderId, Integer totel_fee, String ipAddrStr) throws Exception {
        // 设置访问路径
        HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder");
        // 随机
        String nonce_str = getNonceStr().toUpperCase();
        // 组装请求参数,按照ASCII排序

        String unifiedOrdersign =
                "appid=" + WxPayConstants.APPID +
                        "&body=" + body +
                        "&mch_id=" + WxPayConstants.MCH_ID +
                        "&nonce_str=" + nonce_str +
                        "&notify_url=" + WxPayConstants.NOTIFY_APP_URL +
                        "&out_trade_no=" + orderId +
                        "&spbill_create_ip=" + ipAddrStr +
                        "&total_fee=" + totel_fee.toString() +
                        "&trade_type=" + WxPayConstants.TRADE_TYPE_SCAN +
                        "&key=" + WxPayConstants.KEY;
        // 这个字段是用于之后MD5加密的，字段要按照ascii码顺序排序
        String sign1 = MD5.calcMD5(unifiedOrdersign).toUpperCase();
        String sign = Md5Encrypt.md5(unifiedOrdersign).toUpperCase();
        String sign2 = HMACSHA256.sha256_HMAC(unifiedOrdersign, WxPayConstants.KEY).toUpperCase();
        logger.info("扫二维码支付统一下单参与签名的字符串是：" + unifiedOrdersign + ";扫二维码支付下单MD5值:" + sign + ";扫二维码支付下单Md5Encrypt值:" + sign1 + ";扫二维码支付下单HMACSHA256值:" + sign2);
        // 组装包含openid用于请求统一下单返回结果的XML
        StringBuilder sb = new StringBuilder("");
        sb.append("<xml>");
        setXmlKV(sb, "appid", WxPayConstants.APPID);
        setXmlKV(sb, "body", body);
        setXmlKV(sb, "mch_id", WxPayConstants.MCH_ID);
        setXmlKV(sb, "nonce_str", nonce_str);
        setXmlKV(sb, "notify_url", WxPayConstants.NOTIFY_APP_URL);
        setXmlKV(sb, "out_trade_no", orderId);
        setXmlKV(sb, "spbill_create_ip", ipAddrStr);
        setXmlKV(sb, "total_fee", totel_fee.toString());
        setXmlKV(sb, "trade_type", WxPayConstants.TRADE_TYPE_SCAN);
        setXmlKV(sb, "sign", sign);
        sb.append("</xml>");
        logger.info("扫二维码支付统一下单请求：" + sb);
        // 这个处理是为了防止传中文的时候出现签名错误
        StringEntity reqEntity = new StringEntity(new String(sb.toString().getBytes("UTF-8"), "ISO8859-1"));
        httppost.setEntity(reqEntity);
        CloseableHttpClient httpclient = HttpClients.createDefault();
        CloseableHttpResponse response = httpclient.execute(httppost);
        String strResult = EntityUtils.toString(response.getEntity(), Charset.forName("utf-8"));
        logger.info("扫二维码支付统一下单返回xml：" + strResult);

        return strResult;
    }

    /***
     * 获取扫码支付预下单后，微信回复的prepay_id和trade_type和code_url
     * @param prePayInfoXml
     * @return
     */
    public static Map<String, Object> getPayScanMap(String prePayInfoXml) {
        Map<String, Object> map = new HashMap<String, Object>();
        String result_code = getXmlPara(prePayInfoXml, "result_code");
        String return_code = getXmlPara(prePayInfoXml, "return_code");
        String return_msg = getXmlPara(prePayInfoXml, "return_msg");
        logger.info("result_code是：" + result_code + ";return_code是：" + return_code + ";return_msg是：" + return_msg);

        if ("SUCCESS".equals(result_code) && "SUCCESS".equals(return_code)) {
            // 统一下单返回xml中prepay_id （预支付交易会话标识）
            String prepay_id = getXmlPara(prePayInfoXml, "prepay_id");
            String trade_type = getXmlPara(prePayInfoXml, "trade_type");
            String code_url = getXmlPara(prePayInfoXml, "code_url");
            logger.info("返回的prepay_id是：" + prepay_id + ";返回的trade_type是：" + trade_type + ";返回的code_url是：" + code_url);
            map.put("prepay_id", prepay_id);
            map.put("trade_type", trade_type);
            map.put("code_url", code_url);
            map.put("result_code", result_code);
            map.put("return_code", return_code);
            map.put("return_msg", return_msg);
            logger.info("扫二维码支付统一下单返回map：" + map);
            return map;
        } else {
            map.put("result_code", result_code);
            map.put("return_code", return_code);
            map.put("return_msg", return_msg);
            logger.info("扫二维码支付统一下单未取到预下单号：" + map);
            return map;
        }
    }

    /**
     * 获取微信公众号支付下单返回的参数
     * 根据统一下单返回预支付订单的id和其他信息生成签名并拼装为map（调用微信支付）
     *
     * @param prePayInfoXml
     * @return
     */
    public static Map<String, Object> getPayMap(String prePayInfoXml) {
        Map<String, Object> map = new HashMap<String, Object>();
        String prepay_id = getXmlPara(prePayInfoXml, "prepay_id");
        // 统一下单返回xml中prepay_id
        String timeStamp = String.valueOf((System.currentTimeMillis() / 1000));
        // 1970年到现在的秒数
        String nonceStr = getNonceStr().toUpperCase();
        // 随机数据字符串
        String packageStr = "prepay_id=" + prepay_id;
        String signType = "MD5";

        String paySign1 =
                "appId=" + WxPayConstants.APPID +
                        "&nonceStr=" + nonceStr +
                        "&package=" + packageStr +
                        //"&prepay_id="+prepay_id+
                        "&signType=" + signType +
                        "&timeStamp=" + timeStamp +
                        "&key=" + WxPayConstants.KEY;
        // 注意这里的参数要根据ASCII码排序
        logger.info("getPayMap参与签名的字符串是：" + paySign1);
        String paySign = MD5.calcMD5(paySign1).toUpperCase();
        // 将数据MD5加密
        map.put("appId", WxPayConstants.APPID);
        map.put("timeStamp", timeStamp);
        map.put("nonceStr", nonceStr);
        map.put("packageStr", packageStr);
        map.put("signType", signType);
        map.put("paySign", paySign);
        map.put("prepay_id", prepay_id);
        logger.info("统一下单返回map：" + map);
        return map;
    }

    /**
     * 获取h5支付下单返回的参数
     * 根据统一下单返回预支付订单的id和其他信息生成签名并拼装为map（调用微信支付）
     *
     * @param
     * @return
     */
    public static Map<String, Object> getPayH5Map(String prePayInfoXml) {
        Map<String, Object> map = new HashMap<String, Object>();
        String result_code = getXmlPara(prePayInfoXml, "result_code");
        String return_code = getXmlPara(prePayInfoXml, "return_code");
        logger.info("result_code是：" + result_code + ";return_code是：" + return_code);

        if ("SUCCESS".equals(result_code) && "SUCCESS".equals(return_code)) {
            // 统一下单返回xml中prepay_id （预支付交易会话标识）
            String prepay_id = getXmlPara(prePayInfoXml, "prepay_id");

            String mweb_url = getXmlPara(prePayInfoXml, "mweb_url");
            // 统一下单返回xml中mweb_url （支付跳转链接）,支付成功跳转redirect_url的链接
            //String mweb_url = getXmlPara(prePayInfoXml, "mweb_url") + "&redirect_url=" + URLEncoder.encode(WxPayConstants.paySuccess);
            logger.info("返回的mweb_url应该是：" + mweb_url);
            map.put("prepay_id", prepay_id);
            map.put("mweb_url", mweb_url);
            logger.info("H5统一下单返回map：" + map);
            return map;
        }
        return null;
    }

    /**
     * 获取APP支付下单返回的参数
     * 根据统一下单返回预支付订单的id和其他信息生成签名并拼装为map（调用微信支付）
     *
     * @param
     * @return
     */
    public static Map<String, Object> getPayAPPMap(String prePayInfoXml) {
        Map<String, Object> map = new HashMap<String, Object>();
        String result_code = getXmlPara(prePayInfoXml, "result_code");
        String return_code = getXmlPara(prePayInfoXml, "return_code");
        String return_msg = getXmlPara(prePayInfoXml, "return_msg");
        logger.info("result_code是：" + result_code + ";return_code是：" + return_code + ";return_msg是：" + return_msg);

        if ("SUCCESS".equals(result_code) && "SUCCESS".equals(return_code)) {
            // 统一下单返回xml中prepay_id （预支付交易会话标识）
            String prepay_id = getXmlPara(prePayInfoXml, "prepay_id");
            String trade_type = getXmlPara(prePayInfoXml, "trade_type");
            logger.info("返回的prepay_id是：" + prepay_id + ";返回的trade_type是：" + trade_type);
            map.put("prepay_id", prepay_id);
            map.put("trade_type", trade_type);
            map.put("result_code", result_code);
            map.put("return_code", return_code);
            map.put("return_msg", return_msg);
            logger.info("APP统一下单返回map：" + map);
            return map;
        } else {
            map.put("result_code", result_code);
            map.put("return_code", return_code);
            map.put("return_msg", return_msg);
            logger.info("APP统一下单未取到预下单号：" + map);
            return map;
        }

    }

    /**
     * 修改订单状态，获取微信回调结果
     *
     * @param request
     * @return 应该是流只能用一次的问题
     */
    public static Map getNotifyResult(HttpServletRequest request) {
        String inputLine;
        String notifyXml = "";
        String resXml = "";
        try {
            while ((inputLine = request.getReader().readLine()) != null) {
                notifyXml += inputLine;
            }
            request.getReader().close();
        } catch (Exception e) {
            logger.debug("xml获取失败了：" + e);
            e.printStackTrace();
        }
        logger.debug(notifyXml);

        String appid = getXmlPara(notifyXml, "appid");
        String bank_type = getXmlPara(notifyXml, "bank_type");
        String cash_fee = getXmlPara(notifyXml, "cash_fee");
        String fee_type = getXmlPara(notifyXml, "fee_type");
        String is_subscribe = getXmlPara(notifyXml, "is_subscribe");
        String mch_id = getXmlPara(notifyXml, "mch_id");
        String nonce_str = getXmlPara(notifyXml, "nonce_str");
        String openid = getXmlPara(notifyXml, "openid");
        String out_trade_no = getXmlPara(notifyXml, "out_trade_no");
        String result_code = getXmlPara(notifyXml, "result_code");
        String return_code = getXmlPara(notifyXml, "return_code");
        String sign = getXmlPara(notifyXml, "sign");
        String time_end = getXmlPara(notifyXml, "time_end");
        String total_fee = getXmlPara(notifyXml, "total_fee");
        String trade_type = getXmlPara(notifyXml, "trade_type");
        String transaction_id = getXmlPara(notifyXml, "transaction_id");

        // 根据返回xml计算本地签名
        String localSign =
                "appid=" + appid +
                        "&bank_type=" + bank_type +
                        "&cash_fee=" + cash_fee +
                        "&fee_type=" + fee_type +
                        "&is_subscribe=" + is_subscribe +
                        "&mch_id=" + mch_id +
                        "&nonce_str=" + nonce_str +
                        "&openid=" + openid +
                        "&out_trade_no=" + out_trade_no +
                        "&result_code=" + result_code +
                        "&return_code=" + return_code +
                        "&time_end=" + time_end +
                        "&total_fee=" + total_fee +
                        "&trade_type=" + trade_type +
                        "&transaction_id=" + transaction_id +
                        "&key=" + WxPayConstants.KEY;
        // 注意这里的参数要根据ASCII码 排序
        //将数据MD5加密
        String localSign1 = MD5.calcMD5(localSign).toUpperCase();
        localSign = Md5Encrypt.md5(localSign).toUpperCase();

        logger.debug("微信签名是：" + sign + ";MD5.calcMD5签名是：" + localSign1 + "Md5Encrypt.md5签名：" + localSign);

        if ("SUCCESS".equalsIgnoreCase(result_code) && "SUCCESS".equalsIgnoreCase(return_code)) {
            //成功
            logger.debug("公众号支付成功，out_trade_no(订单号)为：" + out_trade_no + ";公众号支付成功，total_fee(金额)为：" + total_fee + ";公众号支付成功，result_code(回调返回)为：" + result_code + ";公众号支付成功，return_code(用户返回)为：" + return_code);

            resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";

        } else {
            logger.info("返回错误结果码");
            resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[FAIL]]></return_msg>" + "</xml> ";
        }

        //---------------------------------------------------------------------------
        //返回一个map，map中包含了回复给微信的xml和调用北京接口的参数
        Map<String, Object> returnMap = new HashMap<String, Object>();
        returnMap.put("resXml", resXml);
        returnMap.put("out_trade_no", out_trade_no);
        returnMap.put("total_fee", total_fee);
        returnMap.put("result_code", result_code);
        returnMap.put("return_code", return_code);

        returnMap.put("appid", appid);
        returnMap.put("bank_type", bank_type);
        returnMap.put("cash_fee", cash_fee);
        returnMap.put("fee_type", fee_type);

        returnMap.put("is_subscribe", is_subscribe);
        returnMap.put("mch_id", mch_id);
        returnMap.put("nonce_str", nonce_str);
        returnMap.put("openid", openid);

        returnMap.put("sign", sign);
        returnMap.put("time_end", time_end);
        returnMap.put("trade_type", trade_type);
        returnMap.put("transaction_id", transaction_id);

        //---------------------------------------------------------------------------
        return returnMap;
    }

    /**
     * 修改订单状态，获取微信回调结果
     *
     * @param request
     * @return 应该是流只能用一次的问题
     */
    public static Map getH5NotifyResult(HttpServletRequest request) {
        String inputLine;
        String notifyXml = "";
        String resXml = "";
        try {
            while ((inputLine = request.getReader().readLine()) != null) {
                notifyXml += inputLine;
            }
            request.getReader().close();
        } catch (Exception e) {
            logger.debug("xml失败获取：" + e);
            e.printStackTrace();
        }

        logger.debug("收到微信异步回调：" + notifyXml);

        String appid = getXmlPara(notifyXml, "appid");
        String bank_type = getXmlPara(notifyXml, "bank_type");
        String cash_fee = getXmlPara(notifyXml, "cash_fee");
        String fee_type = getXmlPara(notifyXml, "fee_type");
        String is_subscribe = getXmlPara(notifyXml, "is_subscribe");
        String mch_id = getXmlPara(notifyXml, "mch_id");
        String nonce_str = getXmlPara(notifyXml, "nonce_str");
        String openid = getXmlPara(notifyXml, "openid");
        String out_trade_no = getXmlPara(notifyXml, "out_trade_no");
        String result_code = getXmlPara(notifyXml, "result_code");
        String return_code = getXmlPara(notifyXml, "return_code");
        String sign = getXmlPara(notifyXml, "sign");
        String time_end = getXmlPara(notifyXml, "time_end");
        String total_fee = getXmlPara(notifyXml, "total_fee");
        String trade_type = getXmlPara(notifyXml, "trade_type");
        String transaction_id = getXmlPara(notifyXml, "transaction_id");

        // 根据返回xml计算本地签名
        String localSign =
                "appid=" + appid +
                        "&bank_type=" + bank_type +
                        "&cash_fee=" + cash_fee +
                        "&fee_type=" + fee_type +
                        "&is_subscribe=" + is_subscribe +
                        "&mch_id=" + mch_id +
                        "&nonce_str=" + nonce_str +
                        "&openid=" + openid +
                        "&out_trade_no=" + out_trade_no +
                        "&result_code=" + result_code +
                        "&return_code=" + return_code +
                        "&time_end=" + time_end +
                        "&total_fee=" + total_fee +
                        "&trade_type=" + trade_type +
                        "&transaction_id=" + transaction_id +
                        "&key=" + WxPayConstants.KEY;
        // 注意这里的参数要根据ASCII码 排序
        //将数据MD5加密

        localSign = MD5.calcMD5(localSign).toUpperCase();
        logger.info("返回xml原来的签名：" + localSign);

        // 本地计算签名与微信返回签名不同||返回结果为不成功

        if (!sign.equals(localSign) || "FAIL".equals(result_code) || "FAIL".equals(return_code)) {
            logger.info("验证签名失败或支付失败！");
            resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[FAIL]]></return_msg>" + "</xml> ";
        } else {
            resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
            logger.info("支付成功，out_trade_no(订单号)为：" + out_trade_no + ";total_fee(金额)为:" + total_fee + ";result_code(回调返回)为:" + result_code);
        }
        //---------------------------------------------------------------------------
        //返回一个map，map中包含了回复给微信的xml和调用北京接口的参数
        Map<String, Object> returnMap = new HashMap<String, Object>();
        returnMap.put("resXml", resXml);
        returnMap.put("out_trade_no", out_trade_no);
        returnMap.put("total_fee", total_fee);
        returnMap.put("result_code", result_code);
        returnMap.put("return_code", return_code);
        returnMap.put("appid", appid);
        returnMap.put("bank_type", bank_type);
        returnMap.put("cash_fee", cash_fee);
        returnMap.put("fee_type", fee_type);
        returnMap.put("is_subscribe", is_subscribe);
        returnMap.put("mch_id", mch_id);
        returnMap.put("nonce_str", nonce_str);
        returnMap.put("openid", openid);
        returnMap.put("sign", sign);
        returnMap.put("time_end", time_end);
        returnMap.put("trade_type", trade_type);
        returnMap.put("transaction_id", transaction_id);

        //---------------------------------------------------------------------------
        return returnMap;
    }

    /****
     * 获取微信支付结果回调的方法
     * @param request
     * @return
     */
    public static Map getAppNotifyResult(HttpServletRequest request) {
        String inputLine;
        String notifyXml = "";
        String resXml = "";
        try {
            while ((inputLine = request.getReader().readLine()) != null) {
                notifyXml += inputLine;
            }
            request.getReader().close();
        } catch (Exception e) {
            logger.debug("APP-xml获取失败：" + e);
            e.printStackTrace();
        }
        logger.debug("APP-收到微信异步回调：" + notifyXml);
        String appid = getXmlPara(notifyXml, "appid");
        String bank_type = getXmlPara(notifyXml, "bank_type");
        String cash_fee = getXmlPara(notifyXml, "cash_fee");
        String fee_type = getXmlPara(notifyXml, "fee_type");
        String is_subscribe = getXmlPara(notifyXml, "is_subscribe");
        String mch_id = getXmlPara(notifyXml, "mch_id");
        String nonce_str = getXmlPara(notifyXml, "nonce_str");
        String openid = getXmlPara(notifyXml, "openid");
        String out_trade_no = getXmlPara(notifyXml, "out_trade_no");
        String result_code = getXmlPara(notifyXml, "result_code");
        String return_code = getXmlPara(notifyXml, "return_code");
        String sign = getXmlPara(notifyXml, "sign");
        String time_end = getXmlPara(notifyXml, "time_end");
        String total_fee = getXmlPara(notifyXml, "total_fee");
        String trade_type = getXmlPara(notifyXml, "trade_type");
        String transaction_id = getXmlPara(notifyXml, "transaction_id");

        // 根据返回xml计算本地签名
        String localSign =
                "appid=" + appid +
                        "&bank_type=" + bank_type +
                        "&cash_fee=" + cash_fee +
                        "&fee_type=" + fee_type +
                        "&is_subscribe=" + is_subscribe +
                        "&mch_id=" + mch_id +
                        "&nonce_str=" + nonce_str +
                        "&openid=" + openid +
                        "&out_trade_no=" + out_trade_no +
                        "&result_code=" + result_code +
                        "&return_code=" + return_code +
                        "&time_end=" + time_end +
                        "&total_fee=" + total_fee +
                        "&trade_type=" + trade_type +
                        "&transaction_id=" + transaction_id +
                        "&key=" + WxPayConstants.KEY;
        // 注意这里的参数要根据ASCII码 排序
        //将数据MD5加密
        localSign = MD5.calcMD5(localSign).toUpperCase();
        logger.info("返回xml原来的签名：" + localSign);

        // 本地计算签名与微信返回签名不同||返回结果为不成功
        if (!sign.equals(localSign) || "FAIL".equals(result_code) || "FAIL".equals(return_code)) {
            logger.info("APP-验证签名失败或支付失败！");
            resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[FAIL]]></return_msg>" + "</xml> ";
        } else {
            resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
            logger.info("APP-支付成功，out_trade_no(订单号)为：" + out_trade_no + ";total_fee(金额)为:" + total_fee + ";result_code(回调返回)为:" + result_code);
        }
        //---------------------------------------------------------------------------
        //返回一个map，map中包含了回复给微信的xml和调用北京接口的参数
        Map<String, Object> returnMap = new HashMap<String, Object>();
        returnMap.put("resXml", resXml);
        returnMap.put("out_trade_no", out_trade_no);
        returnMap.put("total_fee", total_fee);
        returnMap.put("result_code", result_code);
        returnMap.put("return_code", return_code);
        returnMap.put("appid", appid);
        returnMap.put("bank_type", bank_type);
        returnMap.put("cash_fee", cash_fee);
        returnMap.put("fee_type", fee_type);
        returnMap.put("is_subscribe", is_subscribe);
        returnMap.put("mch_id", mch_id);
        returnMap.put("nonce_str", nonce_str);
        returnMap.put("openid", openid);
        returnMap.put("sign", sign);
        returnMap.put("time_end", time_end);
        returnMap.put("trade_type", trade_type);
        returnMap.put("transaction_id", transaction_id);

        //---------------------------------------------------------------------------
        return returnMap;
    }

    /**
     * 获取32位随机字符串
     *
     * @return
     */
    public static String getNonceStr() {
        String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        StringBuilder sb = new StringBuilder();
        Random rd = new Random();
        for (int i = 0; i < 32; i++) {
            sb.append(str.charAt(rd.nextInt(str.length())));
        }
        return sb.toString();
    }

    /**
     * 插入XML标签
     *
     * @param sb
     * @param Key
     * @param value
     * @return
     */
    public static StringBuilder setXmlKV(StringBuilder sb, String Key,
                                         String value) {
        sb.append("<");
        sb.append(Key);
        sb.append(">");

        sb.append(value);

        sb.append("</");
        sb.append(Key);
        sb.append(">");

        return sb;
    }

    /**
     * 解析XML 获得名称为para的参数值
     *
     * @param xml
     * @param para
     * @return
     */
    public static String getXmlPara(String xml, String para) {
        int start = xml.indexOf("<" + para + ">");
        int end = xml.indexOf("</" + para + ">");

        if (start < 0 && end < 0) {
            return null;
        }
        return xml.substring(start + ("<" + para + ">").length(), end)
                .replace("<![CDATA[", "").replace("]]>", "");
    }

    /**
     * 获取ip地址
     *
     * @param request
     * @return
     */
    public static String getIpAddr(HttpServletRequest request) {
        InetAddress addr = null;
        try {
            addr = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            return request.getRemoteAddr();
        }
        byte[] ipAddr = addr.getAddress();
        String ipAddrStr = "";
        for (int i = 0; i < ipAddr.length; i++) {
            if (i > 0) {
                ipAddrStr += ".";
            }
            ipAddrStr += ipAddr[i] & 0xFF;
        }
        return ipAddrStr;
    }

    /*****
     * 去查询订单的方法
     * @param transaction_id
     * @param out_trade_no
     * @return
     */
    public static String orderQuery(String transaction_id, String out_trade_no) {
        try {
            // 设置访问路径
            HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/pay/orderquery");

            String nonce_str = getNonceStr().toUpperCase();
            // 随机

            //以上两个参数只用选其中一个就行了，侧重用transaction_id，比较准确，但是是异步的怕无法及时
            if (!out_trade_no.equals("")) {
                if (!transaction_id.equals("")) {
                    //两个参数都不为空，就用transaction_id去查询
                    // 组装请求参数,按照ASCII排序
                    String orderQuerysign =
                            "appid=" + WxPayConstants.APPID +
                                    "&mch_id=" + WxPayConstants.MCH_ID +
                                    "&nonce_str=" + nonce_str +
                                    "&transaction_id=" + transaction_id +
                                    "&key=" + WxPayConstants.KEY;// 这个字段是用于之后MD5加密的，字段要按照ascii码顺序排序
                    logger.info("订单查询参与签名的字符串是：" + orderQuerysign);
                    //StringUtil.convertCharset(unifiedOrdersign, "UTF-8", "ISO-8859-1")
                    String sign = MD5.calcMD5(orderQuerysign).toUpperCase();

                    logger.info("订单查询参与签名值是：" + sign);
                    // sign = Md5Encrypt.md5(sign).toUpperCase();
                    // 组装包含openid用于请求统一下单返回结果的XML
                    StringBuilder sb = new StringBuilder("");
                    sb.append("<xml>");
                    setXmlKV(sb, "appid", WxPayConstants.APPID);
                    setXmlKV(sb, "mch_id", WxPayConstants.MCH_ID);
                    setXmlKV(sb, "nonce_str", nonce_str);
                    setXmlKV(sb, "transaction_id", transaction_id);
                    setXmlKV(sb, "sign", sign);
                    sb.append("</xml>");
                    logger.info("订单查询请求：" + sb);
                    StringEntity reqEntity = new StringEntity(new String(sb.toString().getBytes("UTF-8"), "ISO8859-1"));
                    // 这个处理是为了防止传中文的时候出现签名错误
                    httppost.setEntity(reqEntity);
                    CloseableHttpClient httpclient = HttpClients.createDefault();
                    CloseableHttpResponse response = httpclient.execute(httppost);
                    String strResult = EntityUtils.toString(response.getEntity(), Charset.forName("utf-8"));
                    logger.info("订单查询返回xml：" + strResult);

                    return strResult;
                }
                // 只有out_trade_no 不为空时，用out_trade_no去查询
                // 组装请求参数,按照ASCII排序
                String orderQuerysign =
                        "appid=" + WxPayConstants.APPID +
                                "&mch_id=" + WxPayConstants.MCH_ID +
                                "&nonce_str=" + nonce_str +
                                "&out_trade_no=" + out_trade_no +
                                "&key=" + WxPayConstants.KEY;// 这个字段是用于之后MD5加密的，字段要按照ascii码顺序排序
                logger.info("订单查询参与签名的字符串是：" + orderQuerysign);
                //StringUtil.convertCharset(unifiedOrdersign, "UTF-8", "ISO-8859-1")
                String sign = MD5.calcMD5(orderQuerysign).toUpperCase();
                logger.info("订单查询参与签名值是：" + sign);

                // 组装包含openid用于请求统一下单返回结果的XML
                StringBuilder sb = new StringBuilder("");
                sb.append("<xml>");
                setXmlKV(sb, "appid", WxPayConstants.APPID);
                setXmlKV(sb, "mch_id", WxPayConstants.MCH_ID);
                setXmlKV(sb, "nonce_str", nonce_str);
                setXmlKV(sb, "out_trade_no", out_trade_no);
                setXmlKV(sb, "sign", sign);
                sb.append("</xml>");
                logger.info("订单查询请求：" + sb);
                // 这个处理是为了防止传中文的时候出现签名错误
                StringEntity reqEntity = new StringEntity(new String(sb.toString().getBytes("UTF-8"), "ISO8859-1"));
                httppost.setEntity(reqEntity);
                CloseableHttpClient httpclient = HttpClients.createDefault();
                CloseableHttpResponse response = httpclient.execute(httppost);
                String strResult = EntityUtils.toString(response.getEntity(), Charset.forName("utf-8"));
                logger.info("订单查询返回xml：" + strResult);

                return strResult;
            }

        } catch (Exception e) {

            e.printStackTrace();
        }
        return null;
    }

    /*****
     * 获取去查询订单微信的首次回复
     * @param request
     * @return
     */

    public static Map getH5OrderQueryNotifyResult(HttpServletRequest request) {
        String inputLine;
        String notifyXml = "";
        try {
            while ((inputLine = request.getReader().readLine()) != null) {
                notifyXml += inputLine;
            }
            request.getReader().close();
        } catch (Exception e) {
            logger.debug("xml获取失败：" + e);
            e.printStackTrace();
        }
        logger.info("查询订单接收到的xml：" + notifyXml);

        String return_code = getXmlPara(notifyXml, "return_code");
        String return_msg = getXmlPara(notifyXml, "return_msg");
        String appid = getXmlPara(notifyXml, "appid");
        String mch_id = getXmlPara(notifyXml, "mch_id");
        String device_info = getXmlPara(notifyXml, "device_info");
        String nonce_str = getXmlPara(notifyXml, "nonce_str");
        String sign = getXmlPara(notifyXml, "sign");
        String result_code = getXmlPara(notifyXml, "result_code");
        String openid = getXmlPara(notifyXml, "openid");
        String is_subscribe = getXmlPara(notifyXml, "is_subscribe");
        String trade_type = getXmlPara(notifyXml, "trade_type");
        String out_trade_no = getXmlPara(notifyXml, "out_trade_no");
        String bank_type = getXmlPara(notifyXml, "bank_type");
        String total_fee = getXmlPara(notifyXml, "total_fee");
        String fee_type = getXmlPara(notifyXml, "fee_type");
        String transaction_id = getXmlPara(notifyXml, "transaction_id");
        String attach = getXmlPara(notifyXml, "attach");
        String time_end = getXmlPara(notifyXml, "time_end");
        String trade_state = getXmlPara(notifyXml, "trade_state");

        // 根据返回xml计算本地签名
        String localSign =
                "appid=" + appid +
                        "&attach=" + attach +
                        "&bank_type=" + bank_type +
                        "&device_info=" + device_info +
                        "&fee_type=" + fee_type +
                        "&is_subscribe=" + is_subscribe +
                        "&mch_id=" + mch_id +
                        "&nonce_str=" + nonce_str +
                        "&openid=" + openid +
                        "&out_trade_no=" + out_trade_no +
                        "&result_code=" + result_code +
                        "&return_code=" + return_code +
                        "&return_msg=" + return_msg +
                        "&time_end=" + time_end +
                        "&total_fee=" + total_fee +
                        "&trade_state=" + trade_state +
                        "&trade_type=" + trade_type +
                        "&transaction_id=" + transaction_id +
                        "&key=" + WxPayConstants.KEY;// 注意这里的参数要根据ASCII码 排序
        //将数据MD5加密
        localSign = MD5.calcMD5(localSign).toUpperCase();
        logger.debug("查询接口本地签名是：" + localSign + ";查询微信支付签名是：" + sign);

        // 本地计算签名与微信返回签名不同||返回结果为不成功
        if (!sign.equals(localSign)) {
            //签名未通过
            logger.info("验证签名失败或返回错误结果码");
        } else {
            //查询成功！返回结果给前端
            Map<String, Object> returnMap = new HashMap<String, Object>();
            returnMap.put("out_trade_no", out_trade_no);
            returnMap.put("transaction_id", transaction_id);
            returnMap.put("trade_state", trade_state);
            return returnMap;
        }

        return null;
    }

    /*
     * 方法二：推荐，速度最快 判断是否为整数
     *
     * @param str 传入的字符串
     *
     * @return 是整数返回true,否则返回false
     * Pattern.compile("^[-\\+]?[\\d]*$")不要在方法体内直接实现
     */
    private static Pattern NUMBER_PATTERN = Pattern.compile("^[-\\+]?[\\d]*$");

    public static boolean isInteger(String str) {
        Pattern pattern = NUMBER_PATTERN;
        return pattern.matcher(str).matches();
    }

    /***
     * 用于获取用户真实ip的方法
     * @param request
     * @return
     */
    public static String getIp2(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            //多次反向代理后会有多个ip值，第一个ip才是真实ip
            int index = ip.indexOf(",");
            if (index != -1) {
                return ip.substring(0, index);
            } else {
                return ip;
            }
        }
        ip = request.getHeader("X-Real-IP");
        if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            return ip;
        }
        return request.getRemoteAddr();
    }


}
